package fr.jade.fraclite.factory;

import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.Interface;
import org.objectweb.fractal.api.Type;
import org.objectweb.fractal.api.control.BindingController;
import org.objectweb.fractal.api.factory.GenericFactory;
import org.objectweb.fractal.api.factory.InstantiationException;
import org.objectweb.fractal.api.type.ComponentType;
import org.objectweb.fractal.api.type.InterfaceType;
import org.objectweb.fractal.api.type.TypeFactory;
import org.objectweb.fractal.deployment.local.api.IModule;
import org.objectweb.fractal.deployment.local.api.QueryInterface;

import fr.jade.fraclite.GenericComponentInterface;
import fr.jade.fraclite.InitializationContext;
import fr.jade.fraclite.api.ComponentInterface;
import fr.jade.fraclite.api.Controller;

/**
 * Provides a basic implementation of the {@link GenericFactory} interface. <br>
 * <br>
 * <b>Requirements</b>
 * <ul>
 * <li>the types used in the {@link #newFcInstance newFcInstance} method must
 * be instances of {@link ComponentType}.</li>
 * <li>the controller descriptors used in the {@link #newFcInstance
 * newFcInstance} method must be strings referencing "true descriptors", which
 * are loaded with the {@link #_this_weaveableL} loader. Here "true descriptors"
 * means controller descriptors as specified in {@link
 * org.objectweb.fractal.julia.asm.ContextClassGenerator}.</li>
 * </ul>
 */

public class BasicGenericFactory implements Controller, GenericFactory {

	protected Object loader;

	// -------------------------------------------------------------------------
	// PUBLIC constructor and weaveableTF are needed for bootstrap
	// -------------------------------------------------------------------------

	public BasicGenericFactory() {
		loader = this.getClass().getClassLoader();
	}

	public TypeFactory weaveableTF;

	protected MembraneType mt = null;

	/**
	 * Initializes the fields of this mixin and then calls the overriden method.
	 * 
	 * @param ic
	 *            information about the component to which this controller
	 *            object belongs.
	 * @throws InstantiationException
	 *             if the initialization fails.
	 */

	public void initFcController(final InitializationContext ic)
			throws InstantiationException {
		weaveableTF = (TypeFactory) ic.getInterface("type-factory");
	}

	public Component newFcInstance(final Type type,
			final Object controllerDescLabel, final Object contentDesc)
			throws InstantiationException {

		String ctrlDesc = null;
		Object cLoader = null;
		if (controllerDescLabel instanceof Object[]) {
			// For Julia based tools interoperability purpose
			assert ((Object[]) controllerDescLabel)[1] instanceof String : "controllerDesc[1] must be instance of String";

			cLoader = ((Object[]) controllerDescLabel)[0];
			ctrlDesc = (String) ((Object[]) controllerDescLabel)[1];
		} else {
			assert controllerDescLabel instanceof String : "controllerDesc must be instance of String";
			cLoader = loader;
			ctrlDesc = (String) controllerDescLabel;
		}

		if (mt == null)
			mt = new MembraneType(weaveableTF);
		InitializationContext ic = new InitializationContext();
		ic.type = type;
		createContent(contentDesc, ic, cLoader);
		createMembrane(type, ctrlDesc, ic, cLoader);
		initControllers(ic);
		initContent(ic);
		return (Component) ic.getInterface("component");
	}

	protected void initContent(InitializationContext ic)
			throws InstantiationException {
		if (ic.content != null) {
			if (ic.content instanceof BindingController) {
				Object cmp = ic.getInterface("component");
				try {
					((BindingController) ic.content).bindFc("component", cmp);
				} catch (Exception e) {
					throw new ChainedInstantiationException(e,
							"Can't initialize component content");
				}
			}
		}
	}

	protected void initControllers(InitializationContext ic)
			throws InstantiationException {
		for (Interface controller : ic.controllers) {
			Controller ctrlImpl = (Controller) ((ComponentInterface) controller)
					.getFcItfImpl();
			ctrlImpl.initFcController(ic);
		}
	}

	protected void createContent(Object contentDesc, InitializationContext ic,
			Object cLoader) throws InstantiationException {
		assert contentDesc == null || contentDesc instanceof String : "contentDesc must be instance of String";

		if (contentDesc == null) {
			ic.content = null;
		} else {
			ic.content = newImplObject((String) contentDesc, cLoader);
		}
	}

	protected void createMembrane(Type componentType, String controllerDesc,
			InitializationContext ic, Object cLoader)
			throws InstantiationException {
		assert componentType instanceof ComponentType : "type must be instance of ComponentType";

		// create component interface
		String itfImplClass = mt.getComponentInterfaceImpl();
		InterfaceType itfType = mt.getComponentType();
		Object ctrlImpl = newImplObject(itfImplClass, cLoader);
		Component owner = (Component) newInterfaceObject(itfType, null, false,
				ctrlImpl, cLoader);
		ic.controllers.add((Interface) owner);
		ic.interfaces.put("component", (Interface) owner);
		// create controllers
		ComponentType ct = mt.getControlType(controllerDesc);
		for (InterfaceType it : ct.getFcInterfaceTypes()) {
			itfImplClass = mt.getControlImpl(controllerDesc, it);
			ctrlImpl = newImplObject(itfImplClass, cLoader);
			Interface c = (Interface) newInterfaceObject(it, owner, false,
					ctrlImpl, cLoader);

			ic.controllers.add(c);
			ic.interfaces.put(it.getFcItfName(), c);
		}

		boolean hasQItf = false;
		if (ic.content instanceof QueryInterface)
			hasQItf = true;
		// create functional interfaces
		for (InterfaceType it : ((ComponentType) componentType)
				.getFcInterfaceTypes()) {

			Interface itf = null;
			if (hasQItf) {
				if (!it.isFcClientItf()) {
					Object queryItf = ((QueryInterface) ic.content)
							.queryInterface(it.getFcItfName(), it
									.getFcItfSignature());
					if (queryItf != null) {
						// TODO we should or could check that the returned
						// object
						// can be
						// casted into the declared language interface.
						itf = (Interface) newInterfaceObject(it, owner, false,
								queryItf, cLoader);
					}
				}

			}
			if (itf == null) {
				itf = (Interface) newInterfaceObject(it, owner, false,
						ic.content, cLoader);
			}
			ic.interfaces.put(it.getFcItfName(), itf);
			InterfaceType complementaryItfType = getComplementaryInterfaceType(it);
			itf = (Interface) newInterfaceObject(complementaryItfType, owner,
					true, null, cLoader);
			ic.internalInterfaces.put(it.getFcItfName(), itf);
		}
	}

	protected InterfaceType getComplementaryInterfaceType(InterfaceType itfType)
			throws InstantiationException {
		return weaveableTF.createFcItfType(itfType.getFcItfName(), itfType
				.getFcItfSignature(), !itfType.isFcClientItf(), itfType
				.isFcOptionalItf(), itfType.isFcCollectionItf());
	}

	private Object newImplObject(String contentDesc, Object cLoader)
			throws InstantiationException {
		try {
			Class contentClass = null;
			if (cLoader instanceof IModule) {
				contentClass = ((IModule) cLoader)
						.loadClass((String) contentDesc);
			} else if (cLoader instanceof ClassLoader) {
				contentClass = ((ClassLoader) cLoader)
						.loadClass((String) contentDesc);
			}

			if (contentClass != null) {
				return contentClass.newInstance();
			} else {
				throw new InstantiationException("Content class not found: "
						+ contentDesc);
			}
		} catch (ClassNotFoundException e) {
			throw new ChainedInstantiationException(e,
					"Content class not found: " + contentDesc);
		} catch (java.lang.InstantiationException e) {
			throw new ChainedInstantiationException(e,
					"InstantiationException: (" + contentDesc + ")");
		} catch (IllegalAccessException e) {
			throw new ChainedInstantiationException(e,
					"IllegalAccessException: (" + contentDesc + ")");
		}
	}

	protected Object newInterfaceObject(InterfaceType type, Component owner,
			boolean isInternal, Object impl, Object cLoader)
			throws InstantiationException {
		try {
			Set<Class> itfSet = new HashSet<Class>();
			Class bciClass = ComponentInterface.class;
			Class itfClass = null;
			ClassLoader proxyCL = null;

			if (cLoader instanceof IModule) {
				IModule module = (IModule) cLoader;
				itfClass = module.loadClass(type.getFcItfSignature());
				proxyCL = new ClassLoaderWrapper(module);
			} else if (cLoader instanceof ClassLoader) {
				proxyCL = (ClassLoader) cLoader;
				itfClass = proxyCL.loadClass(type.getFcItfSignature());
			}

			itfSet.add(bciClass);
			itfSet.add(itfClass);
			return Proxy.newProxyInstance(proxyCL, itfSet
					.toArray(new Class[] {}), new GenericComponentInterface(
					owner, type.getFcItfName(), type, isInternal, impl));
		} catch (ClassNotFoundException e) {
			throw new InstantiationException(
					"Controller interface class not found: "
							+ type.getFcItfSignature());
		}

	}

	private class ClassLoaderWrapper extends ClassLoader {

		private IModule module = null;

		public ClassLoaderWrapper(IModule module) {
			this.module = module;
		}

		public Class loadClass(String className) throws ClassNotFoundException {
			return module.loadClass(className);
		}

		public URL getResource(String resourceName) {
			return module.getResource(resourceName);
		}

		public InputStream getResourceAsStream(String resourceName) {
			return module.getResourceAsStream(resourceName);
		}

	}
}
